﻿using Bouyei.GeoCore.Converters;
using Bouyei.GeoCore.Geometries;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Bouyei.GeoCore.GeoParsers.GeoPackage
{
    public class GeoPackageHeader
    {
        private const byte ExtentFlags = 0x07 << 1;
        private const byte EndianessFlag = 0x01;

        public byte[] Magic { get; set; }= new byte[] { 0x47, 0x50 };

        public byte Version { get; set; } = 0;

        public int SRID { get; set; }

        public byte Flags { get; set; } = 3;

        public GeoLineSegment Extent { get; set; }

        public CoordinateRange ZRange { get; set; }
        public CoordinateRange MRange { get; set; }

        public ByteOrder byteOrder { get; private set; }

        public int HeaderLength { get; private set; }

        public Ordinates Ordinates
        {
            get
            {
                switch ((Flags & ExtentFlags) >> 1)
                {
                    case 0:
                        return Ordinates.None;
                    case 1:
                        return Ordinates.XY;
                    case 2:
                        return Ordinates.XYZ;
                    case 3:
                        return Ordinates.XYM;
                    case 4:
                        return Ordinates.XYZM;
                    default:
                        throw new ArgumentOutOfRangeException(Flags.ToString());
                }
            }
        }

        public static GeoPackageHeader Read(byte[] buffer, int offset = 0)
        {
            BitExtensions bitExt = null;
            var header = new GeoPackageHeader()
            {
                Magic = new byte[] { buffer[offset], buffer[offset + 1] },
                Version = buffer[offset + 2],
                Flags = buffer[offset + 3]
            };

            header.byteOrder =
                ((header.Flags & EndianessFlag) == 0) ? ByteOrder.BigEndian : ByteOrder.LittleEndian;

            bitExt = new BitExtensions(header.byteOrder);
            header.SRID = bitExt.ToInt32(buffer, offset + 4);
            if (header.Ordinates == Ordinates.None)
            {
                header.HeaderLength = 8;
                header.Extent = new GeoLineSegment(new Coordinate(), new Coordinate());
                header.ZRange = new CoordinateRange();
                header.MRange = new CoordinateRange();
                return header;
            }

            header.Extent = new GeoLineSegment(
                new Coordinate(bitExt.ToDouble(buffer, offset + 8), bitExt.ToDouble(buffer, offset + 24)),
                new Coordinate(bitExt.ToDouble(buffer, offset + 16), bitExt.ToDouble(buffer, offset + 32)));

            header.HeaderLength = 40;
            int _offset = offset + 32;

            //Z OR M
            if ((header.Ordinates & Ordinates.Z) == Ordinates.Z)
            {
                header.ZRange = new CoordinateRange(bitExt.ToDouble(buffer, _offset += 8),
                    bitExt.ToDouble(buffer, _offset += 8));

                header.HeaderLength += 16;
            }
            if ((header.Ordinates & Ordinates.M) == Ordinates.M)
            {
                header.MRange = new CoordinateRange(bitExt.ToDouble(buffer, _offset += 8),
                  bitExt.ToDouble(buffer, _offset += 8));

                header.HeaderLength += 16;
            }

            return header;
        }

        public static byte[] Write(GeoPackageHeader header)
        {
            ByteOrder byteOrder =
            ((header.Flags & 0x01) == 0) ? ByteOrder.BigEndian : ByteOrder.LittleEndian;

            BitExtensions bitExt = new BitExtensions(byteOrder);

            using (MemoryStream ms = new MemoryStream())
            using (BinaryWriter binWriter = new BinaryWriter(ms))
            {
                binWriter.Write(header.Magic);
                binWriter.Write(header.Version);
                binWriter.Write(header.Flags);

                binWriter.Write(bitExt.GetBytes(header.SRID));

                if (header.Ordinates == Ordinates.None)
                {
                    return ms.ToArray();
                }
                binWriter.Write(bitExt.GetBytes(header.Extent.Start.X));
                binWriter.Write(bitExt.GetBytes(header.Extent.End.X));
                binWriter.Write(bitExt.GetBytes(header.Extent.Start.Y));
                binWriter.Write(bitExt.GetBytes(header.Extent.End.Y));

                if ((header.Ordinates & Ordinates.Z) == Ordinates.Z)
                {
                    binWriter.Write(bitExt.GetBytes(header.ZRange.Min));
                    binWriter.Write(bitExt.GetBytes(header.ZRange.Max));
                }
                if ((header.Ordinates & Ordinates.M) == Ordinates.M)
                {
                    binWriter.Write(bitExt.GetBytes(header.MRange.Min));
                    binWriter.Write(bitExt.GetBytes(header.MRange.Max));
                }

                return ms.ToArray();
            }
        }
    }
}
